/*
 * Portions Copyright (c) 2010-11 Talis Inc, Some Rights Reserved
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package org.callimachusproject.rdfa.test;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;

import javanet.staxutils.XMLEventPipe;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stax.StAXResult;
import javax.xml.transform.stream.StreamResult;

import org.callimachusproject.engine.RDFEventReader;
import org.callimachusproject.engine.RDFaReader;
import org.callimachusproject.engine.events.RDFEvent;
import org.callimachusproject.engine.events.Triple;
import org.callimachusproject.engine.model.Literal;
import org.callimachusproject.engine.model.Node;
import org.callimachusproject.engine.model.PlainLiteral;
import org.callimachusproject.engine.model.Term;
import org.callimachusproject.xml.DocumentFactory;
import org.openrdf.model.Graph;
import org.openrdf.model.Resource;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.ValueFactory;
import org.openrdf.model.impl.GraphImpl;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.rio.RDFHandlerException;
import org.openrdf.rio.helpers.RDFHandlerBase;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

/**
 * @author Steve Battle
 */
public class Utility {
	
	static XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
	static {
		xmlInputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
		xmlInputFactory.setProperty(XMLInputFactory.IS_VALIDATING, false);
		xmlInputFactory.setProperty("http://java.sun.com/xml/stream/properties/ignore-external-dtd", true);
	}
	
	// DOM support
	static DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
	static final String DTD_FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
	static {
		documentBuilderFactory.setNamespaceAware(true);
		try {
			documentBuilderFactory.setFeature(DTD_FEATURE, false);
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		}
	}
	
	// XSL support
	static TransformerFactory transformerFactory = TransformerFactory.newInstance();
	static final String INDENT_AMOUNT = "{http://xml.apache.org/xslt}indent-amount";


	public static RDFaReader parseRDFa(File rdfa, String base) throws Exception {
		XMLEventReader xml = xmlInputFactory.createXMLEventReader(new FileReader(rdfa));   
		RDFaReader rdf = new RDFaReader(base, xml, base);
		return rdf;
	}
	
	public static Statement createStatement(Triple t, ValueFactory f) throws Exception {
		Resource s = null;
		Node node = t.getSubject();
		if (node.isIRI()) {
			s = f.createURI(node.stringValue());
		}
		else {
			s = f.createBNode(node.stringValue());
		}
		URI p = f.createURI(t.getPredicate().stringValue());
		Term term = t.getObject();
		Value o = null;
		if (term.isPlainLiteral()) {
			PlainLiteral lit = term.asPlainLiteral();
			o = f.createLiteral(lit.stringValue(), lit.getLang());
		}
		else if (term.isIRI()) {
			o = f.createURI(term.stringValue());
		}
		else if (term.isNode()) {
			o = f.createBNode(term.stringValue());
		}
		else if (term.isLiteral()) {
			Literal typed = term.asLiteral();
			URI uri = f.createURI(typed.getDatatype().stringValue());
			o = f.createLiteral(typed.stringValue(), uri);
		}
		else throw new Exception("unimplemented term: "+term);
		return f.createStatement(s, p, o);
	}
	
	public static void loadRepository(RepositoryConnection con, RDFEventReader rdf) throws Exception {
	    while (rdf.hasNext()) {
	    	RDFEvent e = rdf.next() ;
			if (e.isTriple())
				con.add(createStatement(e.asTriple(),con.getValueFactory()));
	    }
	    rdf.close();
	}
	
	public String load(File file) throws IOException {
		final FileReader reader = new FileReader(file);
		char[] block = new char[4096];
		final StringBuffer buffer = new StringBuffer();
		try {
			int len;
			while ((len = reader.read(block)) >0) {
				buffer.append(block, 0, len);
			}
		} finally {
			reader.close();
		}
		return buffer.toString();
	}
	
	public static Document readDocument(File file) throws Exception {
		if (!file.exists()) return null;
		DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder();
		return builder.parse(file);
	}
	
	public static void write(XMLEventReader xml, OutputStream out) throws Exception {
		Transformer transformer = transformerFactory.newTransformer();
		transformer.setOutputProperty(OutputKeys.INDENT, "yes");
		transformer.setOutputProperty(INDENT_AMOUNT, "2");
		transformer.transform (newSource(xml), new StreamResult(out));
		out.write('\n');
	}
	
	public static Document asDocument(XMLEventReader xml) throws Exception {
		Transformer transformer = transformerFactory.newTransformer();
		transformer.setOutputProperty(OutputKeys.INDENT, "yes");
		transformer.setOutputProperty(INDENT_AMOUNT, "2");
		Document doc = documentBuilderFactory.newDocumentBuilder().newDocument();
		transformer.transform (newSource(xml), new DOMResult(doc));
		return doc;
	}
	
	public static void write(Document doc, OutputStream out) throws Exception {
		Transformer transformer = transformerFactory.newTransformer();
		transformer.transform (new DOMSource(doc), new StreamResult(out));
		out.write('\n');
	}

	public static XMLEventReader asXMLEventReader(org.w3c.dom.Node node) throws Exception {
		XMLEventPipe pipe = new XMLEventPipe(10000);
		Transformer transformer = transformerFactory.newTransformer();
		transformer.setOutputProperty(OutputKeys.INDENT, "yes");
		transformer.setOutputProperty(INDENT_AMOUNT, "1");
		transformer.transform (new DOMSource(node), new StAXResult(pipe.getWriteEnd()));
		return pipe.getReadEnd();
	}

	public static Graph exportGraph(RepositoryConnection con) throws Exception {
		final Graph graph = new GraphImpl();
		con.export(new RDFHandlerBase() {
			public void handleStatement(Statement statement) throws RDFHandlerException {
				graph.add(statement);
			}			
		});
		return graph;
	}

	public static void write(Graph graph, OutputStream out) throws Exception {
		for (Iterator<Statement> i = graph.iterator(); i.hasNext(); ) {
			System.out.println(i.next());
		}
		out.write('\n');
	}
		
	public static Document transform(XMLEventReader xml, Transformer transformer) throws Exception {
		Document doc = documentBuilderFactory.newDocumentBuilder().newDocument();
		transformer.transform(newSource(xml), new DOMResult(doc));
		return doc;
	}

	private static Source newSource(XMLEventReader xml) throws IOException,
			SAXException, ParserConfigurationException, TransformerException {
		return createSource(toByteArrayInputStream(xml), null);
	}

	private static DOMSource createSource(InputStream in, String systemId)
			throws IOException, SAXException, ParserConfigurationException {
		try {
			DocumentFactory factory = DocumentFactory.newInstance();
			if (systemId == null)
				return createSource(factory.parse(in), null);
			return createSource(factory.parse(in, systemId), systemId);
		} finally {
			in.close();
		}
	}

	private static DOMSource createSource(Document node, String systemId) {
		if (systemId == null) {
			String documentURI = ((Document) node).getDocumentURI();
			if (documentURI != null)
				return createSource(node, documentURI);
		}
		if (systemId == null)
			return new DOMSource(node);
		return new DOMSource(node, systemId);
	}

	private static ByteArrayInputStream toByteArrayInputStream(XMLEventReader reader)
			throws TransformerException {
		if (reader == null)
			return null;
		ByteArrayOutputStream output = new ByteArrayOutputStream(8192);
		try {
			XMLEventWriter writer = XMLOutputFactory.newInstance().createXMLEventWriter(output);
			try {
				writer.add(reader);
			} finally {
				reader.close();
				writer.close();
				output.close();
			}
		} catch (XMLStreamException e) {
			throw new TransformerException(e);
		} catch (IOException e) {
			throw new TransformerException(e);
		}
		ByteArrayInputStream input = new ByteArrayInputStream(
				output.toByteArray());
		return input;
	}
	
}
